package com.example.bootintegration.dao.jpa.repository;

import com.synnex.base.jpa.repository.RepositoryUtil;
import io.micrometer.common.util.StringUtils;
import jakarta.persistence.EntityManager;
import jakarta.persistence.metamodel.EntityType;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.SingleTableEntityPersister;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;

/**
 * @author Quintoy
 * @date 2025/1/2
 */
@Transactional
@Slf4j
public class UpdateRepositoryImpl<T, ID> implements UpdateRepository<T, ID> {
    @Override
    public int update(Collection<? extends T> entities) {
        return update(entities, null);
    }



    /**
     *  update set xx, xx, xxx, xx from ...
     * @param entities
     * @param updateFields null represent all field update, else use table field
     * @return
     */
    @Override
    public int update(Collection<? extends T> entities, List<String> updateFields) {
        if (CollectionUtils.isEmpty(entities)) {
            return 0;
        }
        try {
            log.info("update sql, entities size [{}] ",  entities.size());
            long startTime = System.currentTimeMillis();
            Object simpleJpaRepository = ExposeInvocationInterceptor.currentInvocation().getThis();
            EntityManager entityManager = SimpleJpaRepositoryUtil.getEntityManager(simpleJpaRepository);
            EntityInformation<T, ID> entityInformation = SimpleJpaRepositoryUtil.getEntityInformation(simpleJpaRepository);
            Class<T> javaType = entityInformation.getJavaType();
            EntityPersister entityPersister = ((MetamodelImplementor) entityManager.getMetamodel()).entityPersister(javaType);
            // get update sql
            String updateSql = buildUpdateSQL(updateFields, entityPersister);
            log.info("SQL: {}", updateSql);
            int affectedRows = 0;
            if (!CollectionUtils.isEmpty(updateFields)) { // partial update
                List<List<Object>> paramsList = getParamsList(entityManager, entities, updateFields, javaType);
                affectedRows = RepositoryUtil.executeBatchUpdate(entities, updateSql, entityManager, entityPersister, paramsList);
            } else {
                affectedRows = RepositoryUtil.executeBatchUpdate(entities, updateSql, entityManager, entityPersister);
            }
            long endTime = System.currentTimeMillis();
            log.info("update sql, entities size [{}], used time [{}]ms, affected rows [{}] ", entities.size(), endTime - startTime, affectedRows);
            return affectedRows;
        } /*catch (SQLException e) {
            throw new RuntimeException(e);
        }*/ catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static String buildUpdateSQL(List<String> updateFields, EntityPersister entityPersister) {
        String sql = RepositoryUtil.getSQLUpdateStrings(entityPersister);
        if (CollectionUtils.isEmpty(updateFields)) {
            return sql;
        }
        int setIndex = sql.indexOf("set");
        int whereIndex = sql.indexOf("where");
        String prefixSQL = sql.substring(0, setIndex);
        String whereSQL = sql.substring(whereIndex - 1);

        StringBuilder stringBuilder = new StringBuilder(10);
        stringBuilder.append("set ");
        String versionColumnName = ((SingleTableEntityPersister) entityPersister).getVersionColumnName();
        if (entityPersister.isVersioned() && !updateFields.contains(versionColumnName)) {
            updateFields.add(versionColumnName);
        }
        for (String updateField : updateFields) {
            stringBuilder.append(updateField).append(" = ?,");
        }
        stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        return prefixSQL + stringBuilder + whereSQL;
    }

    private List<List<Object>> getParamsList(EntityManager entityManager, Collection<? extends T> entities, List<String> updateFields, Class<T> javaType) throws Exception {
        EntityType<T> entity = entityManager.getMetamodel().entity(javaType);
        List<List<Object>> paramsList = Lists.newArrayList();
        for (T t : entities) {
            List<Object> params = Lists.newArrayList();
            for (String updateField : updateFields) {
                // params.add(getFieldValue(javaType, t, updateField));
                String camelCase = toCamelCase(updateField);
                Field field = (Field) entity.getAttribute(camelCase).getJavaMember();
                params.add(field.get(t));
            }
            paramsList.add(params);
        }
        return paramsList;
    }


    public static String toCamelCase(String underscoreStr) {
        if (StringUtils.isBlank(underscoreStr)) {
            return underscoreStr;
        }
        StringBuilder camelCaseStr = new StringBuilder();
        boolean nextUpperCase = false;
        for (char c : underscoreStr.toCharArray()) {
            if (c == '_') {
                nextUpperCase = true;
            } else {
                if (nextUpperCase) {
                    camelCaseStr.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    camelCaseStr.append(Character.toLowerCase(c));
                }
            }
        }
        return camelCaseStr.toString();
    }


}
